using Gee;
using Gtk;
using Gdk;
using Cairo;

namespace Ribbons {

	/** Gallery of tiles. */
	public class Gallery : Container {

		private Button _up;
		private Button _down;
		private ToggleButton _expand;
		private GalleryPopupWindow _popup;

		private int _first_displayed_tile_index;
		private int _last_displayed_tile_index;
		private int _button_width;

		private Requisition _up_req;
		private Requisition _down_req;
		private Requisition _expand_req;

		private Gdk.Rectangle _tiles_alloc;

		private const double SPACE = 2.0;
		private const double LINE_WIDTH = 1.0;

		/** Fired when a Tile has been selected. */
		public signal void tile_selected (Gallery sender, Tile selected_tile);

		/** Gets or sets the width of tiles. */
		private int _tile_width;
		public int tile_width {
			set {
				_tile_width = value;
				queue_draw ();
			}
			get { return _tile_width; }
		}

		/** Gets or sets the height of tiles. */
		private int _tile_height;
		public int tile_height {
			set {
				_tile_height = value;
				queue_draw ();
			}
			get { return _tile_height; }
		}

		/** Gets or sets the spacing between tiles. */
		private int _tile_spacing;
		public int tile_spacing {
			set {
				_tile_spacing = value;
				queue_draw ();
			}
			get { return _tile_spacing; }
		}

		/** Gets or sets the default number of tiles per row. */
		private int _default_tiles_per_row;
		public int default_tiles_per_row {
			set {
				_default_tiles_per_row = value;
				queue_draw ();
			}
			get { return _default_tiles_per_row; }
		}

		/** Gets or sets the selected Tile. */
		private Tile _selected_tile;
		public Tile selected_tile {
			set {
				if (_popup != null) {
					Gtk.grab_remove (_popup);
					Gdk.pointer_ungrab (0);
					Gdk.keyboard_ungrab (0);

					_popup.hide ();
//					_popup.destroy ();	// XXX-GEETK
					_popup = null;
				}

				if (_selected_tile != null) {
					_selected_tile.selected = false;
				}
				_selected_tile = value;
				if (_selected_tile != null) {
					_selected_tile.selected = true;

					int idx = _tiles.index_of (_selected_tile);
					if (idx != -1) {
						_first_displayed_tile_index = idx / _default_tiles_per_row
														 * _default_tiles_per_row;
						_last_displayed_tile_index = -1;
						update_tiles_layout ();
						queue_draw ();
					}
				}
			}
			get { return _selected_tile; }
		}

		/** Returns all tiles. */
		private Gee.List<Tile> _tiles;
		public Gee.List<Tile> tiles {
			get {
				return _tiles;
			}
		}

		/** Theme used to draw the widget. */
		private Theme _theme = new Theme ();
		public Theme theme {
			set {
				_theme = value;
				queue_draw ();
			}
			get { return _theme; }
		}

		/** Construction method */
		construct {
			set_flags (get_flags () | WidgetFlags.NO_WINDOW);

			add_events (Gdk.EventMask.BUTTON_PRESS_MASK
					| Gdk.EventMask.BUTTON_RELEASE_MASK
					| Gdk.EventMask.POINTER_MOTION_MASK);

			_tiles = new Gee.ArrayList<Tile> ();

			_default_tiles_per_row = 3;
			_first_displayed_tile_index = 0;
			_last_displayed_tile_index = -1;

			_tile_height = 56;
			_tile_width = 72;
			_tile_spacing = 0;
			this.border_width = 2;

			_up = new Button.with_label ("\xe2\x96\xb2");           // U+25B2
			_up.set_parent (this);
			_up.padding = 0;
			_up.clicked.connect (up_clicked);

			_down = new Button.with_label ("\xe2\x96\xbc");         // U+25BC
			_down.set_parent (this);
			_down.padding = 0;
			_down.clicked.connect (down_clicked);
			
			_expand = new ToggleButton.with_label ("\xe2\x86\x93"); // U+2193
			_expand.set_parent (this);
			_expand.padding = 0;
			_expand.value_changed.connect (expand_value_changed);
		}

		/**
		 * Adds a tile before all existing _tiles.
		 *
		 * @param tile  The tile to add.
		 */
		public void prepend_tile (Tile tile) {
			insert_tile (tile, 0);
		}

		/**
		 * Adds a tile after all existing _tiles.
		 *
		 * @param tile  The tile to add.
		 */
		public void append_tile (Tile tile) {
			insert_tile (tile, -1);
		}

		/**
		 * Inserts a tile at the specified location.
		 *
		 * @param tile   The tile to add.
		 * @param index  The index (starting at 0) at which the tile must be
		 *               inserted, or -1 to insert the tile after all existing
		 *               tiles.
		 */
		public void insert_tile (Tile tile, int index) {
			if (index == -1 || index == _tiles.size) {
				_tiles.add (tile);
			} else {
				_tiles.insert (index, tile);
			}

			if (_tiles.size == 1) {
				this.selected_tile = tile;
			}

//			tile.set_parent (this);
			tile.visible = true;
			tile.clicked.connect (tile_clicked);
		}

		/**
		 * Removes the tile at the specified index.
		 *
		 * @param index  Index of the tile to remove.
		 */
		public void remove_tile (int index) {
			var tile = _tiles[index];
			tile.clicked -= tile_clicked;
			tile.unparent ();
			if (_selected_tile == tile) {
				_selected_tile = null;
			}

			_tiles.remove_at (index);
		}

		private void up_clicked () {
			move_up ();
		}
		
		private void down_clicked () {
			move_down ();
		}

		private void expand_value_changed () {
			if (_expand.value) {
				_popup = new GalleryPopupWindow (this);
				_popup.realize.connect (() => {
					int x, y;
					get_parent_window ().get_origin (out x, out y);
					x += this.allocation.x;
					y += this.allocation.y;
					_popup.window.move (x, y);
				});

				_popup.hide.connect (() => {
					_expand.value_changed -= expand_value_changed;
					_expand.value = false;
					_expand.queue_draw ();
					_expand.value_changed.connect (expand_value_changed);
				});
				_popup.show ();

				_popup.button_release_event.connect (on_button_released);
				_popup.add_events (Gdk.EventMask.BUTTON_PRESS_MASK);
				Gtk.grab_add (_popup);

				Gdk.GrabStatus grabbed = Gdk.pointer_grab (_popup.window, true,
								Gdk.EventMask.BUTTON_RELEASE_MASK, null, null, 0);
				if (grabbed == Gdk.GrabStatus.SUCCESS) {
					grabbed = Gdk.keyboard_grab (_popup.window, true, 0);

					if (grabbed != Gdk.GrabStatus.SUCCESS) {
						Gtk.grab_remove (_popup);
						_popup.hide ();
//						_popup.destroy ();	// XXX-GEETK
						_popup = null;
						return;
					}
				} else {
					Gtk.grab_remove (_popup);
					_popup.hide ();
//					_popup.destroy ();	// XXX-GEETK
					_popup = null;
				}
			} else {
				if (_popup != null) {
					Gtk.grab_remove (_popup);
					_popup.hide ();
//					_popup.destroy ();	// XXX-GEETK
					_popup = null;
				}
			}
		}

		private void tile_clicked (Tile sender) {
			if (_selected_tile != null) {
				_selected_tile.selected = false;
			}
			_selected_tile = sender;
			_selected_tile.selected = true;
			on_tile_selected (_selected_tile);
		}

		private void move_up () {
			if (_first_displayed_tile_index > 0) {
				_first_displayed_tile_index = -1;
				_last_displayed_tile_index = _first_displayed_tile_index - 1;
			}
			update_tiles_layout ();
			queue_draw ();
		}

		private void move_down () {
			if (_last_displayed_tile_index < _tiles.size - 1) {
				_first_displayed_tile_index = _last_displayed_tile_index + 1;
				_last_displayed_tile_index = -1;
			}
			update_tiles_layout ();
			queue_draw ();
		}

		/**
		 * Fires the selected_tile event.
		 *
		 * @param selected_tile  The Tile that has been selected.
		 */
		protected void on_tile_selected (Tile selected_tile) {
			tile_selected (this, selected_tile);
		}

		private bool on_button_released (Widget sender, EventButton event) {
			if (_popup != null) {
				Gtk.grab_remove (_popup);
				Gdk.pointer_ungrab (0);
				Gdk.keyboard_ungrab (0);

				_popup.hide ();
				_popup.destroy ();
				_popup = null;
			}
			return false;
		}

		private void update_tiles_layout () {
			Gdk.Rectangle tile_alloc = Gdk.Rectangle ();
			tile_alloc.x = _tiles_alloc.x;
			tile_alloc.y = _tiles_alloc.y;
			tile_alloc.height = _tiles_alloc.height;
			tile_alloc.width = _tile_width;

			int max_tiles = (_tiles_alloc.width + _tile_spacing)
											/ (_tile_width + _tile_spacing);

			if (_first_displayed_tile_index == -1) {
				_first_displayed_tile_index = _last_displayed_tile_index - max_tiles + 1;
				if (_first_displayed_tile_index < 0) {
					_last_displayed_tile_index -= _first_displayed_tile_index;
					_first_displayed_tile_index = 0;
				}
			} else if (_last_displayed_tile_index == -1) {
				_last_displayed_tile_index = _first_displayed_tile_index + max_tiles - 1;
			}

			if (_last_displayed_tile_index >= _tiles.size) {
				_last_displayed_tile_index = _tiles.size - 1;
			}

			_up.enabled = _first_displayed_tile_index > 0;
			_down.enabled = _last_displayed_tile_index < _tiles.size - 1;

			for (int i = 0; i < _first_displayed_tile_index; i++) {
				if (_tiles[i].parent != null) {
					_tiles[i].unparent ();
				}
			}
			for (int i = _last_displayed_tile_index + 1; i < _tiles.size; i++) {
				if (_tiles[i].parent != null) {
					_tiles[i].unparent ();
				}
			}

			for (int i = _first_displayed_tile_index; i <= _last_displayed_tile_index; i++) {
				Tile t = _tiles[i];
				
				if (t.parent == null) {
					t.set_parent (this);
				}

				Requisition req;
				t.size_request (out req);

				t.size_allocate (tile_alloc);
				tile_alloc.x += tile_alloc.width + _tile_spacing;
			}
		}

		protected override void forall_internal (bool include_internals,
		                                         Gtk.Callback callback)
		{
//			if (include_internals) {
				callback (_up);
				callback (_down);
				callback (_expand);
//			}

			for (int i = _first_displayed_tile_index; i <= _last_displayed_tile_index; i++) {
				callback (_tiles[i]);
			}
		}

		protected override void size_request (out Requisition requisition) {
			base.size_request (out requisition);

			_up.size_request (out _up_req);
			_down.size_request (out _down_req);
			_expand.size_request (out _expand_req);

			_button_width = int.max (_up_req.width,
								int.max (_down_req.width, _expand_req.width));
			int btn_height = _up_req.height + _down_req.height + _expand_req.height;

			int count = int.min (_tiles.size, _default_tiles_per_row);
			requisition.width = _button_width + (int) SPACE
								+ 2 * (int) LINE_WIDTH
								+ count * _tile_width
								+ (count + 1) * _tile_spacing
								+ 2 * (int) this.border_width;
			requisition.height = int.max (_tile_height + 2 * (_tile_spacing + (int) LINE_WIDTH),
											btn_height) + 2 * (int) this.border_width;

			if (this.width_request != -1) {
				requisition.width = this.width_request;
			}
			if (this.height_request != -1) {
				requisition.height = this.height_request;
			}
		}

		protected override void size_allocate (Gdk.Rectangle alloc) {
			base.size_allocate (alloc);
			Gdk.Rectangle allocation = alloc;	// XXX-GEETK

			allocation.x += (int) this.border_width;
			allocation.y += (int) this.border_width;
			allocation.width -= 2 * (int) this.border_width;
			allocation.height -= 2 * (int) this.border_width;

			Gdk.Rectangle btn_alloc = Gdk.Rectangle ();
			btn_alloc.width = _button_width;
			btn_alloc.x = allocation.x + allocation.width - btn_alloc.width + 1;

			btn_alloc.y = allocation.y;
			btn_alloc.height = _up_req.height;
			_up.size_allocate (btn_alloc);

			btn_alloc.y += btn_alloc.height;
			btn_alloc.height = _down_req.height;
			_down.size_allocate (btn_alloc);

			btn_alloc.y += btn_alloc.height;
			btn_alloc.height = _expand_req.height;
			_expand.size_allocate (btn_alloc);

			_tiles_alloc.x = allocation.x + (int) LINE_WIDTH + _tile_spacing;
			_tiles_alloc.y = allocation.y + (int) LINE_WIDTH + _tile_spacing;
			_tiles_alloc.width = btn_alloc.x - _tiles_alloc.x - _tile_spacing
											- (int) SPACE - 2 * (int) LINE_WIDTH;
			_tiles_alloc.height = allocation.height - 2 * (_tile_spacing + (int) LINE_WIDTH); 

			update_tiles_layout ();
		}

		protected override bool expose_event (Gdk.EventExpose evnt) {
			var cr = Gdk.cairo_create (this.window);

			cr.rectangle (evnt.area.x, evnt.area.y,
							evnt.area.width, evnt.area.height);
			cr.clip ();
			draw (cr);

//			cr.target.dispose ();	// XXX-GEETK
//			cr.dispose ();

			return base.expose_event (evnt);
		}

		protected void draw (Context cr) {
			var alloc = Rectangle () {
				x = this.allocation.x,
				y = this.allocation.y,
				width = this.allocation.width,
				height = this.allocation.height
			};
			var tiles = Rectangle () {
				x = _tiles_alloc.x - _tile_spacing,
				y = _tiles_alloc.y - _tile_spacing,
				width = _tiles_alloc.width + 2 * _tile_spacing,
				height = _tiles_alloc.height + 2 * _tile_spacing
			};
			_theme.draw_gallery (cr, alloc, tiles, this);
		}

		internal override void remove (Widget widget) {
			// XXX-GEETK
		}
	}
}
